import { createMeterNode, createMeter } from 'src/plugins/web-audio-peak-meter';
export default class AudioMixer {
  public audioCtx: AudioContext;
  public channelList: Channel[];
  constructor() {
    this.audioCtx = new window.AudioContext();
    this.channelList = [];
  }
  public changeVolume(volumn: number, name = 'default') {
    const channel = this.channelList.find((channel) => channel.name === name);
    if (channel) {
      channel.changeVolumn(volumn);
    }
  }
  public resume() {
    this.audioCtx.resume();
  }
  public createChannel(name = 'default'): Channel {
    const channel = new Channel(this.audioCtx, name);
    this.channelList.push(channel);
    return channel;
  }
  public createAudioSourceByBuffer(
    buffer: AudioBuffer,
    channelName = 'default'
  ) {
    const channel = this.channelList.find(
      (channel) => channel.name === channelName
    );
    if (channel) {
      const source = this.audioCtx.createBufferSource();
      source.buffer = buffer;
      source.connect(channel.node);
      return source;
    }
  }
  public createAudioSource(element: HTMLAudioElement, channelName: string) {
    const channel = this.channelList.find(
      (channel) => channel.name === channelName
    );
    if (channel) {
      const source = this.audioCtx.createMediaElementSource(element);
      source.connect(channel.node);
    } else {
      throw new Error(`Can't find channel: ${channelName}`);
    }
  }
  public getAudioBufferFromElement(
    element: HTMLAudioElement
  ): Promise<AudioBuffer> {
    // use this to draw waveform
    return new Promise(async (resolve) => {
      const response = await (await fetch(element.src)).arrayBuffer();
      this.audioCtx.decodeAudioData(response, (buffer) => {
        resolve(buffer);
      });
    });
  }
  public createAudioPeak(container: HTMLDivElement, channelName = 'default') {
    const channel = this.channelList.find(
      (channel) => channel.name === channelName
    );
    if (channel) {
      const meterNode = createMeterNode(channel.node, this.audioCtx);
      createMeter(container, meterNode);
    } else {
      throw new Error(`Can't find channel: ${channelName}`);
    }
  }
}
export class Channel {
  public gainNode: GainNode;
  public analyserNode: AnalyserNode;
  public name: string;
  private audioCtx: AudioContext;
  private audioBufferSrc: AudioBufferSourceNode | null = null;
  private buffer: AudioBuffer | null = null;
  constructor(ctx: AudioContext, name = 'default') {
    this.audioCtx = ctx;
    this.name = name;
    this.gainNode = ctx.createGain();
    this.analyserNode = ctx.createAnalyser();
    this.gainNode.connect(this.analyserNode);
    this.analyserNode.connect(this.audioCtx.destination);
  }
  public getContext() {
    return this.audioCtx;
  }
  public changeVolumn(vol: number) {
    if (this.gainNode) {
      this.gainNode.gain.value = vol;
    }
  }
  public get node(): GainNode {
    return this.gainNode;
  }
  public get analyser(): AnalyserNode {
    return this.analyserNode;
  }
  public createBuffer(duration: number, rate = 44100): AudioBuffer {
    this.buffer = this.audioCtx.createBuffer(1, duration * rate, rate);
    console.log('buffer length:', this.buffer.length);
    return this.buffer;
  }
  public play(position = 0) {
    if (!this.audioBufferSrc) {
      this.audioBufferSrc = this.audioCtx.createBufferSource();
      this.audioBufferSrc.buffer = this.buffer;
      this.audioBufferSrc.connect(this.gainNode);
      this.audioBufferSrc.start(0, position);
    }
  }
  public stop() {
    if (this.audioBufferSrc) {
      this.audioBufferSrc.stop();
      this.audioBufferSrc.disconnect();
      this.audioBufferSrc = null;
    }
  }
}

export function isNeg(number = 0): boolean {
  return number === 0 && 1 / number === -Infinity;
}
export function nidx(idx: number, length: number): number {
  return idx == null
    ? 0
    : isNeg(idx)
    ? length
    : idx <= -length
    ? 0
    : idx < 0
    ? length + (idx % length)
    : Math.min(length, idx);
}
